﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.Objects.DataClasses;
using System.Data.Objects;
using System.Linq;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;
using OnePiece.Core.Exception;
using System.Linq.Dynamic;
using OnePiece.Core.Extension.Paging;
using OnePiece.Core.ModelComponent;
using OnePiece.Core.Resources;
using OnePiece.Core.Extension;

namespace OnePiece.Core.RepositoryComponent
{
    public abstract class RepositoryComponentBase<TEntityObject, TModel, TObjectContext> : IRepositoryComponentBase<TModel>
        where TEntityObject : EntityObject        
        where TObjectContext : ObjectContext
        where TModel : ModelComponentBase
    {


        #region Constructor
        private void LoadUnitySetting()
        {
            //read the unity setting from the web.config
            UnityContainer = new UnityContainer();
            var section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
            section.Containers["RepositoryHelperContainer"].Configure(UnityContainer);

            //load the context
            _context = this.UnityContainer.Resolve<ObjectContext>() as TObjectContext;

        }
        protected RepositoryComponentBase()
        {
            LoadUnitySetting();
        }

        protected RepositoryComponentBase(TObjectContext objectContext)
        {
            LoadUnitySetting();
            _context = objectContext;
        }


        #endregion

        #region Properties

        public UnityContainer UnityContainer { get; set; }

        private TObjectContext _context;
        public TObjectContext Context
        {
            get
            {
                if (_context == null)
                {
                    _context = this.UnityContainer.Resolve<ObjectContext>() as TObjectContext;

                }
                return _context;
            }
            set
            {
                _context = value;
            }
        }



        private string _entitySetName;
        public string EntitySetName
        {
            get
            {
                if (string.IsNullOrEmpty(_entitySetName))
                {
                    var entityObject = (TEntityObject)Activator.CreateInstance(typeof(TEntityObject));
                    var type = entityObject.GetType();
                    _entitySetName = type.Name + "Set";
                }
                return _entitySetName;
            }
        }

        private string _entityKeyName;
        public string EntityKeyName
        {
            get
            {

                if (string.IsNullOrEmpty(_entityKeyName))
                {
                    _entityKeyName = EntitySetName.Substring(0, EntitySetName.Length - 3) + "Id";
                }
                return _entityKeyName;
            }
        }


        public string SpecificEntitySetName { get; set; }

        public string SpecificEntityKeyName { get; set; }

        private string _qualifiedEntitySetName;
        /// <summary>
        /// Get the full name of the EntitySet, e.g. TnanEntities.Customer
        /// Concatenating the Object ContextName and the EntitySetName
        /// </summary>
        public string QualifiedEntitySetName
        {
            get
            {
                if (string.IsNullOrEmpty(_qualifiedEntitySetName))
                {
                    var contextType = Context.GetType();
                    _qualifiedEntitySetName = contextType.Name + "." + EntitySetName;
                }
                return _qualifiedEntitySetName;
            }
        }
        #endregion
        
        #region Update
        public virtual int Update(TModel entity)
        {
            //merge the changes
            this.MergeChanges(entity);

            //update the values
            return Context.SaveChanges();

        } 
        #endregion

        #region Insert
        public virtual int Insert(TModel entity)
        {
            //convert the from dto to ef entity
            var efEntity = this.MapToEntity(entity);

           return Insert(efEntity);

        } 
        #endregion

        #region Delete from Repository
        public virtual int Delete(TModel entity)
        {
            //update the column IsDeleted=true
            entity.IsDeleted = true;

            //update the values
            return this.Update(entity);
        }

        public virtual int Delete(Guid entityId)
        {
            return this.Delete(entityId.ToString());
        }

        public virtual int Delete(string entityId)
        {
            var efEntity = GetEntity(entityId);
            var dtoEntity = this.MapToModel(efEntity);

            //delete the entity
            return this.Delete(dtoEntity);
        } 
        #endregion

        #region Get From Repository
        public virtual TModel GetById(Guid? entityId)
        {
            entityId = entityId ?? Guid.Empty;
            if (entityId == Guid.Empty)
                throw new ObjectDoesNotExistsException(string.Format(TextResource.ObjectDoesNotExist, entityId));

            var entity = GetEntity(entityId);
            

            if (entity == null)
                throw new ObjectDoesNotExistsException(string.Format(TextResource.ObjectDoesNotExist, entityId));

            return this.MapToModel(entity);
            

        }

        public virtual IList<TModel> GetByCriteriaWithSelector(Func<TModel, bool> predicate, Func<TModel, TModel> selectSelector)
        {


            //var entity = GetListQuery()
            //    .Select(selectSelector)
            //    .AsQueryable()
            //    .FirstOrDefault();

            //if (Equals(entity, default(TResult)))
            //{
            //    throw new ObjectDoesNotExistsException(string.Format(TextResource.ObjectDoesNotExist, entityId));
            //}

            //return entity;

            throw new NotImplementedException();
        }

        public virtual IList<TModel> GetAll()
        {
            return this.MapToModel(this.GetListQuery()).ToList();
        }

        public virtual IList<TModel> GetAllPerPage(ref PageExtensionHelper pager)
        {
            return this.MapToModel(this.GetListQuery().Page(ref pager)).ToList();
        }

        public virtual IList<TModel> GetAllByCriteria(Func<TModel, bool> predicate)
        {
            //Convert the predicate into a string
            //return this.GetListQuery().Where(predicate);
            throw new NotImplementedException();

        }

        public virtual IList<TModel> GetAllByCriteriaPerPage(Func<TModel, bool> predicate, ref PageExtensionHelper pager)
        {
            //Convert the predicate into a string
            //return this.GetListQuery().Page(ref pager).Where(predicate);  
            throw new NotImplementedException();
        } 
        #endregion
         
        #region RemoveFromRepository
        public virtual void RemoveFromRepository(TModel entity)
        {
            //merge the changes
            var efEntities = this.MergeChanges(entity);

            //remove
            RemoveFromRepository(efEntities);

        }

        public virtual void RemoveFromRepository(Guid entityId)
        {

            //merge the changes
            var efEntities = GetEntity(entityId);

            //remove
            RemoveFromRepository(efEntities);
        }

        public virtual void RemoveFromRepository(string entityId)
        {
            //merge the changes
            var efEntities = GetEntity(entityId);

            //remove
            RemoveFromRepository(efEntities);
        } 
        #endregion              

        #region Internal Helpers

        public int RemoveFromRepository(TEntityObject entity)
        {
            //remove from the db
            Context.DeleteObject(entity);

            //update the values
            return Context.SaveChanges();
        }

        public int Insert(TEntityObject entity)
        {

            //Add to context
            Context.AddObject(EntitySetName, entity);

            //Save the changes
            return Context.SaveChanges();
        }

        public TEntityObject GetEntity(Guid? entityId)
        {
            var query = GetListQuery()
                .Where("{0}=@0".FormatWith(EntityKeyName), entityId);


            return query.FirstOrDefault();
        }

        public TEntityObject GetEntity(string entityId)
        {
            return GetEntity(new Guid(entityId));
        }


        public T GetEntity<T>(Guid? entityId)
        {
            var query = GetListQuery<T>()
                .Where("{0}=@0".FormatWith(SpecificEntityKeyName), entityId);

            return query.FirstOrDefault();
        }

        public T GetEntity<T>(string entityId)
        {
            return GetEntity<T>(new Guid(entityId));
        }



        /// <summary>
        /// Crete the Queryable object for the current entity
        /// </summary>
        /// <returns></returns>
        public IQueryable<TEntityObject> GetListQuery()
        {
            var efEntity = Context.CreateQuery<TEntityObject>("[" + EntitySetName + "]");

            return efEntity.Where("it.IsDeleted IS NULL OR it.IsDeleted = false");
        }



        /// <summary>
        /// Crete the Queryable object for the specified entity
        /// </summary>
        /// <returns></returns>        
        public IQueryable<T> GetListQuery<T>()
        {
            
            var type = typeof(T);
            SpecificEntitySetName = type.Name + "Set";
            SpecificEntityKeyName = type.Name + "Id";

            var efEntity = Context.CreateQuery<T>("[" + SpecificEntitySetName + "]");
            return efEntity.Where("it.IsDeleted IS NULL OR it.IsDeleted = false");
        }



        /// <summary>
        /// Merge the updated entity the the values from the repository
        /// This is the limitation of EF 3.5, we need to query first then to apply changes 
        /// to take effect the EntityState changes
        /// </summary>                 
        /// <returns></returns>
        public virtual TEntityObject MergeChanges(TModel updatedModel)
        {
            try
            {
                //get he values from the database
                var original = GetEntity(updatedModel.EntityId);

                //pass the value to original to retain the previous value
                original = this.MapToEntity(updatedModel, original);

                return original;
            }
            catch (ObjectDoesNotExistsException)
            {
                return this.MapToEntity(updatedModel);
            }
        }

        #endregion

        #region Mappers

        public virtual TModel MapToModel(TEntityObject entityObject, TModel injectedModel)
        {            
            var obj = injectedModel ??  (TModel)Activator.CreateInstance(typeof(TModel));
            
            obj.IsDeleted = false;
            obj.IsNew = false;
            obj.IsValid = true;

            return obj;
        }

        public abstract TEntityObject MapToEntity(TModel model, TEntityObject injectedEntity);


        public virtual TModel MapToModel(TEntityObject entityObject)
        {
            return MapToModel(entityObject, null);
        }

        public virtual TEntityObject MapToEntity(TModel model)
        {
            return MapToEntity(model, null);
        }

        public virtual IList<TModel> MapToModel(IEnumerable<TEntityObject> entityObjectList, IEnumerable<TModel> injectedModel)
        {

            var entities = (injectedModel == null)
                                        ? new List<TModel>()
                                        : injectedModel.ToList();

            foreach (var o in entityObjectList)
                entities.Add(MapToModel(o));

            return entities;
        }

        public virtual IList<TModel> MapToModel(IEnumerable<TEntityObject> entityObjectList)
        {
            return MapToModel(entityObjectList, null);
        }
        #endregion

    }
}



